Skip to content

Annotate DependencyInjection to make it linker friendly - #40227

Merged
eerhardt merged 3 commits into
dotnet:masterfrom
eerhardt:AnnotateDependencyInjection
Aug 2, 2020
Merged

Annotate DependencyInjection to make it linker friendly#40227
eerhardt merged 3 commits into
dotnet:masterfrom
eerhardt:AnnotateDependencyInjection

Conversation

@eerhardt

@eerhardt eerhardt commented Aug 1, 2020

Copy link
Copy Markdown
Member

Fix #39745

With this change, the remaining (current) ILLinker warnings for the 2 DependencyInjection libraries are:

F:\git\runtime2\src\libraries\Microsoft.Extensions.DependencyInjection\src\ServiceLookup\Expressions\ExpressionResolverBuilder.cs(135,17): Trim analysis warning IL2006: Microsoft.Extensions.DependencyInjection.ServiceLookup.ExpressionResolverBuilder.VisitIEnumerable(IEnumerableCallSite,Object): Call to 'System.Reflection.MethodInfo.MakeGenericMethod' is not recognized.
F:\git\runtime2\src\libraries\Microsoft.Extensions.DependencyInjection\src\ServiceLookup\ILEmit\ILEmitResolverBuilder.cs(216,17): Trim analysis warning IL2006: Microsoft.Extensions.DependencyInjection.ServiceLookup.ILEmitResolverBuilder.VisitIEnumerable(IEnumerableCallSite,ILEmitResolverBuilderContext): Call to 'System.Reflection.MethodInfo.MakeGenericMethod' is not recognized.
F:\git\runtime2\src\libraries\Microsoft.Extensions.DependencyInjection\src\ServiceLookup\CallSiteFactory.cs(241,17): Trim analysis warning IL2006: Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateOpenGeneric(ServiceDescriptor,Type,CallSiteChain,Int32): Calling to 'System.Type.MakeGenericType' on unrecognized value.

@ghost

ghost commented Aug 1, 2020

Copy link
Copy Markdown

Tagging subscribers to this area: @eerhardt, @maryamariyan
See info in area-owners.md if you want to be subscribed.

@eerhardt
eerhardt merged commit a5159b1 into dotnet:master Aug 2, 2020
@eerhardt
eerhardt deleted the AnnotateDependencyInjection branch August 2, 2020 18:01
@davidfowl

Copy link
Copy Markdown
Member

@ENikS

ENikS commented Aug 2, 2020

Copy link
Copy Markdown
Contributor

Is this for DI libraries or client code? Will it remove client constructors marked for injection?

@davidfowl

Copy link
Copy Markdown
Member

DI libraries. Today if you link the entire application it will remove constructors use in DI registrations because the linker can't see the which constructors are in use:

var services = new ServiceCollection();
services.AddSingleton<IFoo, Foo>();
var sp = services.BuildServiceProvider();
var foo = sp.GetService<IFoo>();
foo.Bar();

This would fail before this PR because Foo's constructor would be removed after linking. The annotations instruct the linker to preserve constructors of the generic second argument (the implementation type in this case).

@ENikS

ENikS commented Aug 2, 2020

Copy link
Copy Markdown
Contributor

But what about constructors marked with [InjectionConstructor] attributes? I am assuming these will be gone as well?
Some classes are annotated instead of registering them and never touched by xx.AddXXX() methods.

@davidfowl

Copy link
Copy Markdown
Member

@ENikS I don't know if there's a pattern to preserve those. Usually attribute only approaches require assembly scanning, which is also not linker friendly. It's possible that pattern is just incompatible and will result in the user needing to manually preserve those types.

cc @vitek-karas @MichalStrehovsky

@ENikS

ENikS commented Aug 2, 2020

Copy link
Copy Markdown
Contributor

Without that MEF, MEF2, Unity and probable some other are not compatible with the pattern.

@davidfowl thank you for heads up!

@eerhardt

eerhardt commented Aug 2, 2020

Copy link
Copy Markdown
Member Author

Without that MEF, MEF2, Unity and probable some other are not compatible with the pattern.

Correct. We haven't gotten to making MEF and MEF2 linker friendly. It isn't scheduled for .NET 5, but it may be included in .NET 6.

@seesharper

Copy link
Copy Markdown

@davidfowl Thanks for letting us know about this.

So to preserve the constructors for a given type, the type needs to be mentioned in a method taking a Type parameter (or generic Type parameter )annotated with the DynamicallyAccessedMembers? If so, is that the only way to salvage these types? :)

@MichalStrehovsky

Copy link
Copy Markdown
Member

If so, is that the only way to salvage these types? :)

It's the only way that works with the dataflow analysis within the linker.

The problem is basically that code like:

var  o = Activator.CreateInstance(someType);

may or may not work after trimming, depending on whether the default constructor for someType was preserved. By default, you'll get a warning at the time trimming runs for code like this. The warning will say "hey, there's a piece of reflection that I can't analyze. the code may or may not work at runtime".

You can get rid of that warning four ways:

  1. You annotate things with the new attributes like in this pull request so that you'll end up with an annotation on someType that guarantees the default constructor was kept. This is the preferred solution, because it both makes the API "just work" (if linker can reason about the annotated pieces of code), and you got rid of the warning.
  2. You suppress the warning with a suppression attribute and ensure the default constructor is kept through other means. E.g. add a direct call to the constructor somewhere. This is not great because by adding a warnings suppression, you're giving up on linker's warnings in the method and it's now your responsibility to make sure the API calls succeed after trimming.
  3. You get rid of reflection. E.g. use a source generator.
  4. You don't trim and mark the API with a new RequiresUnreferencedCodeAttribute. Linker will warn whenever a trimmed app calls into such API (this is to give a better user experience - instead of user getting a warning about a stray use of reflection in a library, they get a message that the library author made a deliberate choice not to be trimming friendly).

@davidfowl

Copy link
Copy Markdown
Member

Does RequiresUnreferencedCodeAttribute disable trimming on the assembly/type/member specified?

@eerhardt

eerhardt commented Aug 3, 2020

Copy link
Copy Markdown
Member Author

Does RequiresUnreferencedCodeAttribute disable trimming on the assembly/type/member specified?

No. It is a way to mark an API as unsafe for trimming. This lets the caller know that they should either call a different API or they need to do extra work to make this call safe (e.g. explicitly preserve types/methods/properties/etc).

For example Assembly.Load(string | byte[]) methods will be marked with RequiresUnreferencedCodeAttribute.

@ENikS

ENikS commented Aug 3, 2020

Copy link
Copy Markdown
Contributor

You annotate things with the new attributes like in this pull request so that you'll end up with an annotation on someType that guarantees the default constructor was kept. This is the preferred solution, because it both makes the API "just work" (if linker can reason about the annotated pieces of code), and you got rid of the warning.

Does it have to be new attribute? Can you use existing MEF attributes?
These are available on every framework and would spare a lot of refactoring for project owners. We are talking about thousands of types to annotate...

Perhaps you could add an interface, so something like Unity could add to injection attributes as well?

Without support for custom constructor annotations you are cutting out huge chunk of corporate users that use third party IOC containers. Perhaps this could be revisited?

@davidfowl

davidfowl commented Aug 3, 2020

Copy link
Copy Markdown
Member

Nothing is being cut, those applications will still work with less aggressive trimming modes, they just won't be able to trim the assemblies completely (or at all).

BTW, this trimming mode isn't the default and we're still a while away from being able to trim our own framework fully. We're just getting started here.

@ENikS

ENikS commented Aug 3, 2020

Copy link
Copy Markdown
Contributor

I understand, just trying to make sure this scenario is considered from the beginning and is not an afterthought when it is too late to change

@davidfowl

Copy link
Copy Markdown
Member

See dotnet/linker#1402

@ipjohnson

Copy link
Copy Markdown

@davidfowl thanks for the heads up.

Can we apply DynamicallyAccessedMembers just to the api surface or do we need to put the attribute everywhere?

@davidfowl

Copy link
Copy Markdown
Member

Can we apply DynamicallyAccessedMembers just to the api surface or do we need to put the attribute everywhere?

You can apply it to just the API surface but the linker warns if it isn't applied transitively.

@seesharper

Copy link
Copy Markdown

Thanks @MichalStrehovsky for such a detailed explanation.👍

Is there any way we can start to test this? Is it possible to enable this using the preview 7 bits?

I've tried dotnet publish -c Release -r osx-x64 --self-contained true -p:PublishSingleFile=false -p:PublishTrimmed=true on a console app , but that did not seems to yield any warnings nor did it trim unreferenced types.

Finally I was just wondering if this is something that is mainly meant for Xamarin apps or do guys you see this as general feature that eventually can be applied to all .Net 5 apps?

@davidfowl

Copy link
Copy Markdown
Member

@seesharper I made this for you https://github.com/davidfowl/LinkedAspNetCoreApplication. Run it with dotnet publish -r osx-x64

@seesharper

Copy link
Copy Markdown

Wow, that was fast. Thanks a lot, David. I'll have a look first thing tomorrow. Again, I gotta hand it to you guys. The way that you involve the community early on new features is just priceless. 👍💪 Really appreciate it

Jacksondr5 pushed a commit to Jacksondr5/runtime that referenced this pull request Aug 10, 2020
* Annotate DependencyInjection to make it linker friendly

Fix dotnet#39745
@karelz karelz added this to the 5.0.0 milestone Aug 18, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Dec 7, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Annotate DependencyInjection to make it linker friendly

9 participants